package br.usjt.mazerobot.pc;

import br.usjt.mazerobot.commons.navegacao.Mapa;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.channels.FileChannel;
import java.util.Arrays;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;

public class ManterMapa {

    //Diretorio default onde os arquivos serão salvos
    private final String DIRETORIO = "mapas";
    //Extensao que representa um mapa
    public final String EXTENSAO = ".mzr";
    private Mapa mapa;
    private File arquivo;
    private JFileChooser seletor;
    private String mensagemErro;

    /**
     * Construtor
     */
    public ManterMapa() {
        //subString a partir de 1 para remover "." da extensao
        FileNameExtensionFilter filtro = new FileNameExtensionFilter("Mapas *".concat(EXTENSAO), EXTENSAO.substring(1));
        seletor = new JFileChooser();
        seletor.setFileFilter(filtro);

        //Verifica se a pasta default existe
        arquivo = new File(DIRETORIO);
        if (!arquivo.isDirectory()) {
            arquivo.mkdir();
        }
    }

    /**
     * Salva o objeto <i>mapa</i> em um arquivo binario na pasta default de mapas definido no atributo <i>DIRETORIO</i>.
     * @param mapa - objeto que sera persistido em um arquivo
     */
    public boolean salvarMapa(Mapa mapa) {
        mensagemErro = "";
        String nomeArquivo = solicitaNome("Salvar como", "Digite um nome para o mapa:");

        if (nomeArquivo != null && !nomeArquivo.isEmpty()) {
            arquivo = new File(DIRETORIO, nomeArquivo.trim().concat(EXTENSAO));
            return salvar(arquivo, mapa);
        }
        return false;
    }

    /**
     * Exporta o objeto <i>mapa</i> para um diretorio escolhido pelo usuario. 
     * @param mapa - objeto que sera exportado
     */
    public boolean exportarMapa(Mapa mapa) {
        mensagemErro = "";
        seletor.setDialogTitle("Exportar Mapa");
        if (seletor.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
            arquivo = new File(seletor.getSelectedFile().getAbsolutePath().concat(EXTENSAO));
            return salvar(arquivo, mapa);
        }
        return false;
    }

    /**
     * Carrega um objeto <i>mapa</i> existente na pasta default de mapas
     * @param nomeArquivo
     * @return uma instancia do objeto <i>mapa</i>, retorna 
     * null caso o arquivo não seja encontrado
     */
    public Mapa carregarMapa(String nomeArquivo) {
        mensagemErro = "";
        try {
            arquivo = new File(DIRETORIO, nomeArquivo.concat(EXTENSAO));
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(arquivo));
            //deserializa um objeto do tipo Mapa
            this.mapa = (Mapa) ois.readObject();
            ois.close();
            return mapa;

        } catch (ClassNotFoundException e) {
            mensagemErro = "Erro inesperado.";
        } catch (FileNotFoundException e) {
            mensagemErro = "Mapa não encontrado.";
        } catch (IOException e) {
            mensagemErro = "Não foi possível carregar o mapa.";
        }
        return null;
    }

    /**
     * Importa um arquivo do tipo <i>mapa</i> para a pasta default de mapas , o usuario é responsavel por informar a origem do arquivo.
     */
    public boolean importarMapa() {
        mensagemErro = "";
        seletor.setDialogTitle("Importar Mapa");

        //Se algum arquivo do tipo .mzr foi selecionado
        if (seletor.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
            String nomeArquivo = seletor.getSelectedFile().getName();
            
            if(!nomeArquivo.endsWith(EXTENSAO)){
                mensagemErro = "Tipo de arquivo inválido.";
                return false;
            }
            
            arquivo = new File(DIRETORIO, nomeArquivo);
            boolean arquivoValido = !arquivo.exists();

            while (!arquivoValido) {
                nomeArquivo = solicitaNome("Já existe um mapa com esse nome", "Digite um novo nome:");
                //verifica se o nome é valido
                if (nomeArquivo != null && nomeArquivo.trim().length() > 0) {
                    arquivo = new File(DIRETORIO, nomeArquivo.concat(EXTENSAO));
                    arquivoValido = !arquivo.exists();
                } else {
                    arquivoValido = false;
                    break;
                }
            }

            if (arquivoValido) {
                try {
                    return copiarArquivo(seletor.getSelectedFile(), arquivo);
                } catch (IOException e) {
                    mensagemErro = "Não foi possível importar o mapa.";
                }
            }
        }
        return false;
    }

    /**
     * Retorna o nome de todos os mapas contidos no diretorio default de mapas
     */
    public String[] listarMapas() {
        File diretorio = new File(DIRETORIO);
        String[] lista;
        FilenameFilter filtro = new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(EXTENSAO);
            }
        };
        lista = diretorio.list(filtro);
        //Ordena por nome
        Arrays.sort(lista);
        return lista;
    }

    /**
     * Exclui um arquivo existente na pasta default de mapas
     * @param nomeArquivo - o arquivo que sera excluido
     * @return true se excluir , return false se não for possível a exclusão
     */
    public boolean excluirMapa(String nomeArquivo) {
        arquivo = new File(DIRETORIO, nomeArquivo.concat(EXTENSAO));
        return arquivo.delete();
    }

    /**
     * Altera o nome do arquivo
     * @param nomeAntigo
     * @param novoNome
     * @return true se conseguiu alterar ,false se não foi possível renomear
     */
    public boolean renomearMapa(String nomeAntigo) {
        arquivo = new File(DIRETORIO, nomeAntigo.concat(EXTENSAO));

        String novoNome = solicitaNome("Renomear mapa", "Digite o novo nome:");

        if (novoNome == null || novoNome.trim().length() == 0) {
            mensagemErro = "";
            return false;
        }

        File novoArquivo = new File(DIRETORIO, novoNome.trim().concat(EXTENSAO));

        if (novoArquivo.exists()) {
            mensagemErro = "Já existe um mapa com esse nome";
            return false;
        }

        if (arquivo.renameTo(novoArquivo)) {
            return true;
        } else {
            mensagemErro = "Não foi possivel renomear";
            return false;
        }
    }

    /**
     * Salva um objeto <i>mapa</i> na forma binaria , o objeto serializado é gravado para manter seu estado atual. 
     * @param arquivo - indica o nome e diretorio onde o arquivo sera salvo
     * @param mapa - objeto que sera persistido
     */
    private boolean salvar(File arquivo, Mapa mapa) {
        if (arquivo.exists()) {
            mensagemErro = "Já existe um mapa com esse nome";
        } else {
            try {
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(arquivo));
                oos.writeObject(mapa);
                oos.close();
                return true;
            } catch (FileNotFoundException e) {
                mensagemErro = "O arquivo não foi encontrado";
            } catch (IOException e) {
                mensagemErro = "Não foi possível salvar o mapa";
            }
        }
        return false;
    }

    /**
     * Faz uma copia do conteudo de um arquivo para outro , dados como data da criação não são mantidos.
     * 
     * @param arquivoOrigem - contém os dados que serão copiados
     * @param arquivoDestino - contém informações de diretorio e nome do arquivo que recebera a copia
     * @throws IOException
     */
    private boolean copiarArquivo(File arquivoOrigem, File arquivoDestino) throws IOException {

        if (!arquivoDestino.exists()) {
            //Cria um stream para ler o arquivo de origem
            FileInputStream origem = new FileInputStream(arquivoOrigem);
            //Cria um stream para gravar a copia do arquivo
            FileOutputStream destino = new FileOutputStream(arquivoDestino);

            //responsavel por manipular os arquivos
            FileChannel fcOrigem = origem.getChannel();
            FileChannel fcDestino = destino.getChannel();

            //faz a copia de todos os bytes( 0 à fcOrigem.size()) de um arquivo para o outro
            fcOrigem.transferTo(0, fcOrigem.size(), fcDestino);
            return true;
        } else {
            mensagemErro = "Já existe um mapa com esse nome.";
            return false;
        }

    }

    public String getMensagemErro() {
        return mensagemErro;
    }

    private String solicitaNome(String titulo, String mensagem) {
        return JOptionPane.showInputDialog(null, mensagem, titulo, JOptionPane.INFORMATION_MESSAGE);
    }
}
